<?php
/**
 *  NevuxBB - Free Forum Software
 *  Copyright (C) 2008, 2010 NevuxBB (No one specific author)
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Main Core
 * File: core_class.php
 * Purpose: General user and forum tasks
 * Todo: Expand
 *
 */

/**
* Is the user system core
*/
class CoreUserSystem {
	/**
	* Fetch's user's username
	*
	* @access public
	* @return string or false on fail
	*/
	public function getUsername() {
		if(self::isLoggedIn()) {
			// we are logged in
			return $_SESSION['User']['user_name'];
		} else {
			// not logged in, return guest
			return false;	
		}
	} 
	
	/**
	* Fetch's user's ID
	*
	* @access public
	* @return int
	*/		
	public function getUserId() {
		if(self::isLoggedIn()) {
			// we are logged in
			return $_SESSION['User']['user_id'];
		} else {
			// not logged in, return guest
			return false;
		}
	}
	
	/**
	* Fetch's user's level
	*
	* @access public
	* @return int
	*/		
	public function getUserlevel() {
		#$iUserLevel = $_SESSION['User']['user_group']; needs to be modified later
		$iUserLevel = $_SESSION['User']['user_level'];
	
		if(self::isLoggedIn()) {
			return $iUserLevel;
		}	
	}
	
	/**
	* Fetch's user's template
	*
	* @access public
	* @return string
	*/		
	public function getUsertemplate() {
		$iUserTemplate = $_SESSION['User']['user_template'];
		
		// are we logged in?
		if(self::isLoggedIn()) {
			// return what we selected
			return $iUserTemplate;
		} else {
			// guest, return default template
			return SystemSettings::getDefaultTemplate();
		}
	
	}
	
	/**
	* Fetch's user's language
	*
	* @access public
	* @return string
	*/		
	public function getUserLang() {
		$iUserLang = $_SESSION['User']['user_language'];

		// are we logged in?
		if(self::isLoggedIn()) {
			if(empty($iUserLang)) {
				// we haven't set a custom language
				return SystemSettings::getDefaultLanguage();
			} else {
				// select our custom language
				return $iUserLang;
			}
		} else {
			// relay back to board default
			return SystemSettings::getDefaultLanguage();
		}
	}

	/**
	* Checks if a user is logged in
	*
	* @access public
	* @return boolean
	*/		
	public function isLoggedIn() {
		if(isset($_SESSION['User'])) {
			// we are logged in
			return true;
		} else {
			// we aren't logged in
			return false;
		}
	}
	
	/**
	* Unseralises the user-configuration data
	*
	* @access private
	* @return boolean
	*/		
	protected function unserailizeUserConfig() {
		if($this->isLoggedIn()) {
			// is a real user of the system
			$_SESSION['User']['user_config'] = unserialize($_SESSION['User']['user_configurations']);
			
			// done
			return true;
		} else {
			// not real
			return false;
		}
	}
	
	/**
	* Reserialises the user configuration and merges it back into the database
	*
	* @access private
	* @return boolean
	*/		
	private function reserailizeUserConfig() {
		if($this->isLoggedIn()) {
			// is a real user of the system
			$new_data = serialize($_SESSION['User']['user_config']);
			
			// move it back into the database
			if(Db_Abstraction::query("UPDATE `nbb_users` SET `user_configurations` = " . Db_Abstraction::clean($new_data) . " WHERE `user_id` = '" . $this->getUserId() . "' LIMIT 1;")) {
				// successful
				return true;
			} else {
				// something went wrong and therefore wasn't permanent
				return false;
			}
		} else {
			// not real
			return false;
		}
	}
	
	/**
	* Return or insert user configuration data
	*
	* @param string $paramter
	* @param object $data
	* @param boolean $overwrite
	* @access public
	* @return boolean
	*/		
	public function userConfigSettings($parameter, $data = NULL, $overwrite = FALSE) {
		if($this->isLoggedIn()) {
			// is a real user of the system, what needs to be done?
			if(!$data) {
				// wants to update a setting
				if($overwrite) {
					// have to overwrite the original
					$_SESSION['User']['user_config'][$paramter] = $data;
					
					// make changes permanent
					if($this->reserailizeUserConfig()) {
						// worked
						return true;
					} else {
						// didn't go to plan, failed to write
						return false;
					}
				} else {
					// if it exists, don't overwrite
					if(isset($_SESSION['User']['user_config'][$parameter])) {
						// is set, not going to modify
						return false;
					} else {
						// not set, create
						$_SESSION['User']['user_config'][$paramter] = $data;
						
						// make changes permanent
						if($this->reserailizeUserConfig()) {
							// worked
							return true;
						} else {
							// didn't go to plan, failed to write
							return false;
						}
					}
				}
			} else {
				// just return one
				return $_SESSION['User']['user_config'][$paramter];
			}
		} else {
			// not real
			return false;
		}
	}

	/**
	* Returns if the user is an active admin
	*
	* @access public
	* @return boolean
	*/		
	public function getAdminSession() {
		if(!isset($_SESSION['User']['acp_md5']) || !isset($_COOKIE['acp_md5_cookie']) || !$_SESSION['User']['acp_code_passed']) {
			// we haven't met all the critera
			// admin session is not valid
			return false;
		} else {
			if($_SESSION['User']['acp_md5'] != $_COOKIE['acp_md5_cookie'] && $_SESSION['User']['acp_code_passed'] !== true) {
				// still some mismatching criteria (perhaps even hack attempt?)
				return false;
			} else {
				// we are logged in as we meet the criteria
				return true;
			}
		}
	}
	
	/**
	* Logs the admin in
	*
	* @param string $username
	* @param string $password
	* @param string $ip
	* @param string $browser
	* @param string $time
	* @access public
	* @return boolean
	*/	
	public function logAdminIn($username, $password, $ip, $browser, $time) {
		if(self::getUserlevel() == 3) {
			// are we already logged in?
			if(self::getAdminSession()) {
				// already logged in
				return 2;
			} else {
				// proceed to logging in
				// is this a real user to start with?
				$acp_cu_sql = "SELECT COUNT(user_id) AS `users` FROM `nbb_users` WHERE `user_name` = '" . Db_Abstraction::clean($username) . "' AND `user_password` = '" . md5($password . SystemSettings::getSaltKey()) . "' LIMIT 1";
				$acp_cu_result = Db_Abstraction::fetch_array($acp_cu_sql);
				
				if($acp_cu_result['users'] > 0) {
					// is a result
					// generate the md5 key
					$acp_md5 = md5($username . SystemSettings::getSaltKey() . $ip . '.127' . $browser . $time . '7321');
					
					// set this to cookie and session
					if(setcookie('acp_md5_cookie', $acp_md5, time() + 3600)) {
						// set session information
						$_SESSION['User']['acp_md5'] = $acp_md5;
						$_SESSION['User']['acp_login_time'] = time();
	
						// set successfully
						return true;
					} else {
						// failed to set cookie
						return 3;
					}
				} else {
					// not a real user
					return 1;
				}
			}
		} else {
			// not even an admin? why is this function being called?
			// log this hacking attempt
			SystemSettings::addNewAttempt($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_URI'], 'hack', array('user_string' => $agent[0], 'user_string_long' => implode($agent), 'message' => 'was attempting to login as an admin, their username is ' . CoreUserSystem::getUsername()));
			
			// didn't login
			return false;
		}
	}
	
	/**
	* Generate a security key
	*
	* @access public
	* @return string
	*/	
	public function generateAdminSecurityKey() {
		// generate the administration security key
		$key = SystemSettings::generateRandStr(5);
		
		// put this to the session
		$_SESSION['User']['acp_code'] = $key;
		$_SESSION['User']['acp_code_passed'] = false;
		$_SESSION['User']['acp_skip_login'] = true;
		
		// return the key
		return $key;
	}
	
	/**
	* Checks is a username exists
	*
	* @param string $username
	* @return boolean
	* @access protected
	*/		
	protected function isUsername($username) {
		// set query
		$sql = "SELECT `user_name` FROM `members` WHERE `user_name` = '" . Db_Abstraction::clean($username) . "' LIMIT 1";

		// execute the sql
		$results = Db_Abstraction::query($sql);
		$rows = Db_Abstraction::num_rows($results);
		
		// any results?
		if($rows > 0) {
			// does exist
			return true;
		} else {
			// doesn't exist
			return false;
		}
	}

	/**
	* Checks is an email exists
	*
	* @param string $email
	* @return boolean
	* @access protected
	*/		
	protected function isEmail($email) {
		// set query
		$sql = "SELECT `user_email` FROM `members` WHERE `user_email` = '" . Db_Abstraction::clean($email) . "' LIMIT 1";
		
		// execute the sql
		$results = Db_Abstraction::query($sql);
		$rows = DB_Abstraction::num_rows($results);
		
		// any results?
		if(count($aRow) > 0) {
			// does exist
			return true;
		} else {
			// doesn't exist
			return false;
		}
	}
}

/**
* Is a further extension to the user system
*/
class BaseUserSystem extends CoreUserSystem {
	private $m_aUserInformation = array();
	
	/**
	* Initialise the class
	*/
	function __construct() {
		session_start();
		$this->sanitize_globals();
		
		// log this persons current session
		self::logCurrentSession();
		
		// fill the global stats buffer
		ForumSettings::regenerateGS();
		ForumSettings::perform_session_cleanup();
		
		// unserialise the user data to make it accessible straight-away
		self::unserailizeUserConfig();
	}
	
	/**
	* Logs the current session
	*
	* @access private
	*/	
	private function logCurrentSession() {
		// is this a bot?
		$agent = explode('/', $_SERVER['HTTP_USER_AGENT']);
		$bt_sql = "SELECT COUNT(nbb_bot_id) AS `exists` FROM `nbb_bots` WHERE `nbb_bot_string` = '" . Db_Abstraction::clean($agent[0]) . "' LIMIT 1";
		$bt_res = Db_Abstraction::fetch_array($bt_sql);
		
		if($bt_res['exists'] > 0) {
			// is a real bot
			if(CoreUserSystem::isLoggedIn()) {
				// they shouldn't be logged in, log them out
				// log this hacking attempt
				SystemSettings::addNewAttempt($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_URI'], 'hack', array('user_string' => $agent[0], 'user_string_long' => implode($agent), 'message' => 'was logged in with username ' . CoreUserSystem::getUsername()));
				
				// now log them out
				self::logout();
			}
			
			// add them to the online database
			$sql = "SELECT COUNT(session_info) AS `num` FROM `nbb_sessions` WHERE `session_info` = '" . Db_Abstraction::clean($agent[0]) . "' AND `session_type` = '2'";
			$result = Db_Abstraction::fetch_array($sql);
			
			if($result['num'] < 1) {
				// insert a new record
				$sql = "INSERT INTO `nbb_sessions` (`session_info`, `session_type`) VALUES ('" . Db_Abstraction::clean($agent[0]) . "', '2')";
				Db_Abstraction::query($sql);
			} else {
				// update the current record
				$sql = "UPDATE `nbb_sessions` SET `session_lupdate` = NOW() WHERE `session_info` = '" . Db_Abstraction::clean($agent[0]) . "' AND `session_type` = '2' LIMIT 1";
				Db_Abstraction::query($sql);
			}
		} else {
			// is a user/guest
			// insert them into the sessions table
			if(CoreUserSystem::isLoggedIn()) {
				// add or update their info
				$username = Db_Abstraction::clean(CoreUserSystem::getUsername());
				$sql = "SELECT COUNT(session_info) AS `num` FROM `nbb_sessions` WHERE `session_info` = '" . $username . "' AND `session_type` = '1'";
				$result = Db_Abstraction::fetch_array($sql);
				
				if($result['num'] < 1) {
					// insert a new record
					$sql = "INSERT INTO `nbb_sessions` (`session_info`, `session_type`) VALUES ('" . $username . "', '1')";
					Db_Abstraction::query($sql);
				} else {
					// update the current record
					$sql = "UPDATE `nbb_sessions` SET `session_lupdate` = NOW() WHERE `session_info` = '" . $username . "' AND `session_type` = '1' LIMIT 1";
					Db_Abstraction::query($sql);
				}
			} else {
				// add or update their info
				$ip_address = Db_Abstraction::clean($_SERVER['REMOTE_ADDR']);
				$sql = "SELECT COUNT(session_info) AS `num` FROM `nbb_sessions` WHERE `session_info` = '" . $ip_address . "' AND `session_type` = '0'";
				$result = Db_Abstraction::fetch_array($sql);
				
				if($result['num'] < 1) {
					// insert a new record
					$sql = "INSERT INTO `nbb_sessions` (`session_info`, `session_type`) VALUES ('" . $ip_address . "', '0')";
					Db_Abstraction::query($sql);
				} else {
					// update the current record
					$sql = "UPDATE `nbb_sessions` SET `session_lupdate` = NOW() WHERE `session_info` = '" . $ip_address . "' AND `session_type` = '0' LIMIT 1";
					Db_Abstraction::query($sql);
				}
			}
		}
	}
	
	
	/**
	* Logs a user in
	*
	* @param string $szUsername
	* @param string $szPassword
	* @return boolean
	* @access public
	*/		
	public function login($szUsername, $szPassword) {
		$szSQL = "SELECT * FROM `nbb_users` WHERE `user_name` = '" . Db_Abstraction::clean($szUsername) . "' AND `user_password` = '" . md5($szPassword . SystemSettings::getSaltKey()) . "' LIMIT 1";
		
		// is this the right info?
		$pResults = Db_Abstraction::query($szSQL);
		
		// do we have any results?
		if(Db_Abstraction::num_rows($pResults) > 0) {
			// execute query
			$aResults = DB_Abstraction::fetch_assoc($pResults);
			
			if(!$aResults) {
				// something went wrong
				// can't log in
				return false;
			} else {
				// put their info into a session
				foreach($aResults as $szKey => $szRecord) {
					$_SESSION['User'][$szKey] = $szRecord;
				}
					
				// was a success
				return true;
			}
		}
		
		// no results
		return false;
	}	

	/**
	* Registers a new username
	*
	* @param string $szUsername
	* @param string $szPassword1
	* @param string $szPassword2
	* @param string $szEmail
	* @return boolean
	* @access public
	*/		
	public function register($szUsername, $szPassword1, $szPassword2, $szEmail) {
		if($this->doPasswordsMatch($szPassword1, $szPassword2) && $this->isUsername($szUsername) && $this->isEmail($szEmail)) {
			// all matches, go on
			$szSQL = "INSERT INTO `members` (user_name, user_password, user_email) VALUES ('" . $szUsername . "', '" . md5($szPassword1 . SystemSettings::getSaltKey()) . "', '" . $szEmail . "')";
			
			// execute query
			Db_Abstraction::query($szSQL);	
			
			// success
			return true;
		} else {
			// failed, something went wrong
			return false;
		}	
	}

	/**
	* Logs a user out
	*
	* @return boolean
	* @access public
	*/	
	public function logout() {
		if(self::isLoggedIn()) {
			// remove them from the sessions table
			$rmsql = "DELETE FROM `nbb_sessions` WHERE `session_info` = '" . CoreUserSystem::getUsername() . "' AND `session_type` = '1'";
			Db_Abstraction::query($rmsql);	
			
			// delete the session
			session_destroy();
			unset($_SESSION);
			
			// logged out
			return true;
		} else {
			// can't log out if you're not logged in
			return false;
		}
	}
	
	/**
	* Sanitises the incoming data
	*
	* @return boolean
	* @access public
	*/	
	private function sanitize_globals() {
		// turn magic quotes off
		@ini_set('magic_quotes_runtime', 0);
		@ini_set('magic_quotes_sybase', 0);
		@ini_set('magic_quotes_gpc', 0);
		
		// "sanitise" these items/arrays
		$this->prepare_global($_GET);
		$this->prepare_global($_POST);
		$this->prepare_global($_COOKIE);
		$this->prepare_global($_REQUEST);
		$this->prepare_global($_ENV);
		$this->prepare_global($_SERVER);
		$this->prepare_global($_SESSION);
		$this->prepare_global($_FILES);
		
		
		// sanitised
		return true;
	}
	
	/**
	* Prepare each array of $_
	*
	* @return boolean
	* @access private
	*/
	private function prepare_global($global = array()) {
		if(!empty($global) && is_array($global)) {
			foreach($global as $key => $val) {
				if(!empty($global[$key]) && is_array($global[$key])) {
					foreach($global[$key] as $k => $v) {
						$global[$this->sanitize_key($key)][$this->sanitize_key($k)] = $this->sanitize_val($v);
					}
					
					unset($k, $v);
				} else {
					$global[$this->sanitize_key($key)] = $this->sanitize_val($val);
				}
			}
			
			unset($key, $val);
			
			// completed sanitisation
			return true;
		} else {
			// is empty and is not an array
			return false;
		}
	}
	
	/**
	* Prepare each array of $_
	*
	* @return string
	* @access private
	*/
	private function sanitize_key($key) {
		$key = preg_replace("/[\r\n]/", '', $key);
		$key = htmlspecialchars($key, ENT_QUOTES);
		
		return $key;
	}
	
	/**
	* Properly sanitise the item
	*
	* @return string/array
	* @access private
	*/
	private function sanitize_val($val) {
		if(is_array($val)) {
			$val = array_map(array(&$this, 'sanitize_val'), $val);
		} else {
			if(!is_numeric($val) || $val[0] == '0' || is_string($val)) {
				if((bool)@ini_get('magic_quotes_gpc') == '1') {
					$val = stripslashes($val);
				}
				
				$val = mysql_real_escape_string($val);
				$val = trim($val);
				$val = htmlspecialchars($val, ENT_QUOTES);
			} elseif($val = strval(intval($val))) {
				$val = intval($val);
			} elseif(is_float($val)) {
				$val = floatval($val);
			}
		}
		
		// return the value
		return $val;
	}
}

?>